/*
** $Id: lmathlib.c,v 1.119 2016/12/22 13:08:50 roberto Exp $
** Standard mathematical library
** See Copyright Notice in lua.h
*/

#define lmathlib_c
#define LUA_LIB

#include "lprefix.h"


#include <stdlib.h>
#include <math.h>

#include "lua.h"

#include "lauxlib.h"
#include "lualib.h"


#undef PI
#define PI  (l_mathop(3.141592653589793238462643383279502884))


#if !defined(l_rand)        /* { */
#if defined(LUA_USE_POSIX)
#define l_rand()    random()
#define l_srand(x)  srandom(x)
#define L_RANDMAX   2147483647  /* (2^31 - 1), following POSIX */
#else
#define l_rand()    rand()
#define l_srand(x)  srand(x)
#define L_RANDMAX   RAND_MAX
#endif
#endif              /* } */


static int math_abs( lua_State* L ) {
    if ( lua_isinteger( L, 1 ) ) {
        lua_Integer n = lua_tointeger( L, 1 );

        if ( n < 0 ) {
            n = ( lua_Integer )( 0u - ( lua_Unsigned )n );
        }

        lua_pushinteger( L, n );
    }
    else {
        lua_pushnumber( L, l_mathop( fabs )( luaL_checknumber( L, 1 ) ) );
    }

    return 1;
}

static int math_sin( lua_State* L ) {
    lua_pushnumber( L, l_mathop( sin )( luaL_checknumber( L, 1 ) ) );
    return 1;
}

static int math_cos( lua_State* L ) {
    lua_pushnumber( L, l_mathop( cos )( luaL_checknumber( L, 1 ) ) );
    return 1;
}

static int math_tan( lua_State* L ) {
    lua_pushnumber( L, l_mathop( tan )( luaL_checknumber( L, 1 ) ) );
    return 1;
}

static int math_asin( lua_State* L ) {
    lua_pushnumber( L, l_mathop( asin )( luaL_checknumber( L, 1 ) ) );
    return 1;
}

static int math_acos( lua_State* L ) {
    lua_pushnumber( L, l_mathop( acos )( luaL_checknumber( L, 1 ) ) );
    return 1;
}

static int math_atan( lua_State* L ) {
    lua_Number y = luaL_checknumber( L, 1 );
    lua_Number x = luaL_optnumber( L, 2, 1 );
    lua_pushnumber( L, l_mathop( atan2 )( y, x ) );
    return 1;
}


static int math_toint( lua_State* L ) {
    int valid;
    lua_Integer n = lua_tointegerx( L, 1, &valid );

    if ( valid ) {
        lua_pushinteger( L, n );
    }
    else {
        luaL_checkany( L, 1 );
        lua_pushnil( L ); /* value is not convertible to integer */
    }

    return 1;
}


static void pushnumint( lua_State* L, lua_Number d ) {
    lua_Integer n;

    if ( lua_numbertointeger( d, &n ) ) { /* does 'd' fit in an integer? */
        lua_pushinteger( L, n );    /* result is integer */
    }
    else {
        lua_pushnumber( L, d );    /* result is float */
    }
}


static int math_floor( lua_State* L ) {
    if ( lua_isinteger( L, 1 ) ) {
        lua_settop( L, 1 );    /* integer is its own floor */
    }
    else {
        lua_Number d = l_mathop( floor )( luaL_checknumber( L, 1 ) );
        pushnumint( L, d );
    }

    return 1;
}


static int math_ceil( lua_State* L ) {
    if ( lua_isinteger( L, 1 ) ) {
        lua_settop( L, 1 );    /* integer is its own ceil */
    }
    else {
        lua_Number d = l_mathop( ceil )( luaL_checknumber( L, 1 ) );
        pushnumint( L, d );
    }

    return 1;
}


static int math_fmod( lua_State* L ) {
    if ( lua_isinteger( L, 1 ) && lua_isinteger( L, 2 ) ) {
        lua_Integer d = lua_tointeger( L, 2 );

        if ( ( lua_Unsigned )d + 1u <= 1u ) { /* special cases: -1 or 0 */
            luaL_argcheck( L, d != 0, 2, "zero" );
            lua_pushinteger( L, 0 ); /* avoid overflow with 0x80000... / -1 */
        }
        else {
            lua_pushinteger( L, lua_tointeger( L, 1 ) % d );
        }
    }
    else
        lua_pushnumber( L, l_mathop( fmod )( luaL_checknumber( L, 1 ),
                                             luaL_checknumber( L, 2 ) ) );

    return 1;
}


/*
** next function does not use 'modf', avoiding problems with 'double*'
** (which is not compatible with 'float*') when lua_Number is not
** 'double'.
*/
static int math_modf( lua_State* L ) {
    if ( lua_isinteger( L, 1 ) ) {
        lua_settop( L, 1 ); /* number is its own integer part */
        lua_pushnumber( L, 0 ); /* no fractional part */
    }
    else {
        lua_Number n = luaL_checknumber( L, 1 );
        /* integer part (rounds toward zero) */
        lua_Number ip = ( n < 0 ) ? l_mathop( ceil )( n ) : l_mathop( floor )( n );
        pushnumint( L, ip );
        /* fractional part (test needed for inf/-inf) */
        lua_pushnumber( L, ( n == ip ) ? l_mathop( 0.0 ) : ( n - ip ) );
    }

    return 2;
}


static int math_sqrt( lua_State* L ) {
    lua_pushnumber( L, l_mathop( sqrt )( luaL_checknumber( L, 1 ) ) );
    return 1;
}


static int math_ult( lua_State* L ) {
    lua_Integer a = luaL_checkinteger( L, 1 );
    lua_Integer b = luaL_checkinteger( L, 2 );
    lua_pushboolean( L, ( lua_Unsigned )a < ( lua_Unsigned )b );
    return 1;
}

static int math_log( lua_State* L ) {
    lua_Number x = luaL_checknumber( L, 1 );
    lua_Number res;

    if ( lua_isnoneornil( L, 2 ) ) {
        res = l_mathop( log )( x );
    }
    else {
        lua_Number base = luaL_checknumber( L, 2 );
#if !defined(LUA_USE_C89)

        if ( base == l_mathop( 2.0 ) ) {
            res = l_mathop( log2 )( x );
        }
        else
#endif
            if ( base == l_mathop( 10.0 ) ) {
                res = l_mathop( log10 )( x );
            }
            else {
                res = l_mathop( log )( x ) / l_mathop( log )( base );
            }
    }

    lua_pushnumber( L, res );
    return 1;
}

static int math_exp( lua_State* L ) {
    lua_pushnumber( L, l_mathop( exp )( luaL_checknumber( L, 1 ) ) );
    return 1;
}

static int math_deg( lua_State* L ) {
    lua_pushnumber( L, luaL_checknumber( L, 1 ) * ( l_mathop( 180.0 ) / PI ) );
    return 1;
}

static int math_rad( lua_State* L ) {
    lua_pushnumber( L, luaL_checknumber( L, 1 ) * ( PI / l_mathop( 180.0 ) ) );
    return 1;
}


static int math_min( lua_State* L ) {
    int n = lua_gettop( L ); /* number of arguments */
    int imin = 1;  /* index of current minimum value */
    int i;
    luaL_argcheck( L, n >= 1, 1, "value expected" );

    for ( i = 2; i <= n; i++ ) {
        if ( lua_compare( L, i, imin, LUA_OPLT ) ) {
            imin = i;
        }
    }

    lua_pushvalue( L, imin );
    return 1;
}


static int math_max( lua_State* L ) {
    int n = lua_gettop( L ); /* number of arguments */
    int imax = 1;  /* index of current maximum value */
    int i;
    luaL_argcheck( L, n >= 1, 1, "value expected" );

    for ( i = 2; i <= n; i++ ) {
        if ( lua_compare( L, imax, i, LUA_OPLT ) ) {
            imax = i;
        }
    }

    lua_pushvalue( L, imax );
    return 1;
}

/*
** This function uses 'double' (instead of 'lua_Number') to ensure that
** all bits from 'l_rand' can be represented, and that 'RANDMAX + 1.0'
** will keep full precision (ensuring that 'r' is always less than 1.0.)
*/
static int math_random( lua_State* L ) {
    lua_Integer low, up;
    double r = ( double )l_rand() * ( 1.0 / ( ( double )L_RANDMAX + 1.0 ) );

    switch ( lua_gettop( L ) ) { /* check number of arguments */
    case 0: {  /* no arguments */
        lua_pushnumber( L, ( lua_Number )r ); /* Number between 0 and 1 */
        return 1;
    }

    case 1: {  /* only upper limit */
        low = 1;
        up = luaL_checkinteger( L, 1 );
        break;
    }

    case 2: {  /* lower and upper limits */
        low = luaL_checkinteger( L, 1 );
        up = luaL_checkinteger( L, 2 );
        break;
    }

    default:
        return luaL_error( L, "wrong number of arguments" );
    }

    /* random integer in the interval [low, up] */
    luaL_argcheck( L, low <= up, 1, "interval is empty" );
    luaL_argcheck( L, low >= 0 || up <= LUA_MAXINTEGER + low, 1,
                   "interval too large" );
    r *= ( double )( up - low ) + 1.0;
    lua_pushinteger( L, ( lua_Integer )r + low );
    return 1;
}


static int math_randomseed( lua_State* L ) {
    l_srand( ( unsigned int )( lua_Integer )luaL_checknumber( L, 1 ) );
    ( void )l_rand(); /* discard first value to avoid undesirable correlations */
    return 0;
}


static int math_type( lua_State* L ) {
    if ( lua_type( L, 1 ) == LUA_TNUMBER ) {
        if ( lua_isinteger( L, 1 ) ) {
            lua_pushliteral( L, "integer" );
        }
        else {
            lua_pushliteral( L, "float" );
        }
    }
    else {
        luaL_checkany( L, 1 );
        lua_pushnil( L );
    }

    return 1;
}


/*
** {==================================================================
** Deprecated functions (for compatibility only)
** ===================================================================
*/
#if defined(LUA_COMPAT_MATHLIB)

static int math_cosh( lua_State* L ) {
    lua_pushnumber( L, l_mathop( cosh )( luaL_checknumber( L, 1 ) ) );
    return 1;
}

static int math_sinh( lua_State* L ) {
    lua_pushnumber( L, l_mathop( sinh )( luaL_checknumber( L, 1 ) ) );
    return 1;
}

static int math_tanh( lua_State* L ) {
    lua_pushnumber( L, l_mathop( tanh )( luaL_checknumber( L, 1 ) ) );
    return 1;
}

static int math_pow( lua_State* L ) {
    lua_Number x = luaL_checknumber( L, 1 );
    lua_Number y = luaL_checknumber( L, 2 );
    lua_pushnumber( L, l_mathop( pow )( x, y ) );
    return 1;
}

static int math_frexp( lua_State* L ) {
    int e;
    lua_pushnumber( L, l_mathop( frexp )( luaL_checknumber( L, 1 ), &e ) );
    lua_pushinteger( L, e );
    return 2;
}

static int math_ldexp( lua_State* L ) {
    lua_Number x = luaL_checknumber( L, 1 );
    int ep = ( int )luaL_checkinteger( L, 2 );
    lua_pushnumber( L, l_mathop( ldexp )( x, ep ) );
    return 1;
}

static int math_log10( lua_State* L ) {
    lua_pushnumber( L, l_mathop( log10 )( luaL_checknumber( L, 1 ) ) );
    return 1;
}

#endif
/* }================================================================== */



static const luaL_Reg mathlib[] = {
    {"abs",   math_abs},
    {"acos",  math_acos},
    {"asin",  math_asin},
    {"atan",  math_atan},
    {"ceil",  math_ceil},
    {"cos",   math_cos},
    {"deg",   math_deg},
    {"exp",   math_exp},
    {"tointeger", math_toint},
    {"floor", math_floor},
    {"fmod",   math_fmod},
    {"ult",   math_ult},
    {"log",   math_log},
    {"max",   math_max},
    {"min",   math_min},
    {"modf",   math_modf},
    {"rad",   math_rad},
    {"random",     math_random},
    {"randomseed", math_randomseed},
    {"sin",   math_sin},
    {"sqrt",  math_sqrt},
    {"tan",   math_tan},
    {"type", math_type},
#if defined(LUA_COMPAT_MATHLIB)
    {"atan2", math_atan},
    {"cosh",   math_cosh},
    {"sinh",   math_sinh},
    {"tanh",   math_tanh},
    {"pow",   math_pow},
    {"frexp", math_frexp},
    {"ldexp", math_ldexp},
    {"log10", math_log10},
#endif
    /* placeholders */
    {"pi", NULL},
    {"huge", NULL},
    {"maxinteger", NULL},
    {"mininteger", NULL},
    {NULL, NULL}
};


/*
** Open math library
*/
LUAMOD_API int luaopen_math( lua_State* L ) {
    luaL_newlib( L, mathlib );
    lua_pushnumber( L, PI );
    lua_setfield( L, -2, "pi" );
    lua_pushnumber( L, ( lua_Number )HUGE_VAL );
    lua_setfield( L, -2, "huge" );
    lua_pushinteger( L, LUA_MAXINTEGER );
    lua_setfield( L, -2, "maxinteger" );
    lua_pushinteger( L, LUA_MININTEGER );
    lua_setfield( L, -2, "mininteger" );
    return 1;
}

